/*
 * =====================================================================================
 *
 *       Filename:  epoll.cpp
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  07/23/2012 09:36:57 PM
 *       Revision:  none
 *       Compiler:  gcc/g++
 *
 *         Author:  wangtengchao , wangtengchao AT gmail.com
 *   Organization:  
 *
 * =====================================================================================
 */

#ifdef linux
#include"epoll.h"
#include"socket.h"
#include "dispatcher.h"
#include<unistd.h>//for close
#ifdef NET_DEBUG
 #include<cstdio>
#endif
#include<errno.h>
using namespace inm::net;

Epoll::Epoll() :
    epfd_(epoll_create1(EPOLL_CLOEXEC)),
    epollEventVector_(InitialMaxEvents)
{
	if(epfd_ < 0)
    {
        ;//epoll create error! write log
    }
    isRunning_ = false;
}

Epoll::~Epoll()
{
	close(epfd_);
}

void Epoll::registerEvent(int fd, const short &et)
{//et can have multi flags  e.g. ACCEPT_EVENT | READ_EVENT
#ifdef NET_DEBUG
    printf("register %d \n",et);
#endif
    epoll_event ev;
    ev.data.fd = fd;
   // ev.events = EPOLLET;//edge trigger
    ev.events = 0;//must give ev.evnts a initialize value
    if(et & ACCEPT_EVENT)
    {
        ev.events |= EPOLLIN ;
    }
    if(et & READ_EVENT)
    {
        ev.events |= EPOLLIN;
    }
    if(et & WRITE_EVENT)
    {
        ev.events |= EPOLLOUT;
    }

   // ev.events |= EPOLLERR;  epoll will always set this ,so don't need do it
    ev.events |= EPOLLRDHUP;//socket peer shutdown

    if(epoll_ctl(epfd_,EPOLL_CTL_ADD,fd,&ev) <0)
    {
       #ifdef NET_DEBUG
        printf("epoll_ctl error\n");
       #endif
    }

}
void Epoll::updateEvent(int fd, const short &et)
{//if delete all event .we should call removeEvent to delete ev in epoll
    epoll_event ev ;
    if(et&ACCEPT_EVENT)
    {
        ev.events |= EPOLLIN ;
    }
    if(et&READ_EVENT)
    {
        ev.events |= EPOLLIN;
    }
    if(et&WRITE_EVENT)
    {
        ev.events |= EPOLLOUT;
    }
    if(ev.events == 0)
    {//delete all events so should delete epoll_event
        removeEvent(fd,0);
    }
    if(epoll_ctl(epfd_,EPOLL_CTL_MOD,fd,&ev) <0)
    {
       #ifdef NET_DEBUG
        printf("epoll_ctl error\n");
       #endif
    }

}

void Epoll::removeEvent(int fd, const short &et)
{//et can be empty because epoll_ctl(epfd,EPOLL_CTL_DEL,fd,xx) will delete the fd form epoll
    struct epoll_event ev;
    //not use buf linux before 2.6.9 need pass a ev even though it is NULL
    //after linux 2.6.9   we can pass NULL to the last argument
    if(epoll_ctl(epfd_,EPOLL_CTL_DEL,fd,&ev) <0)
    {
       #ifdef NET_DEBUG
        printf("epoll_ctl error\n");
       #endif
    }
}

void Epoll::handleEvents(int timeout)
{
	int ret = 0;
	isRunning_ = true;//when dispatcher call handleevents ,it means start
	while(true)
	{
        dispatcher_->doTasks();
        if(isRunning_ && (ret = epoll_wait(epfd_,&(*epollEventVector_.begin()),static_cast<int>(epollEventVector_.size()),timeout)) > 0)
        {//isRunning_ can be set false in doTasks()

            for(int i=0;i < ret ;i++)
			{
                //it must be defined here.
                EventHandlerPtr it  = dispatcher_->getEventHandlerByFd(epollEventVector_[i].data.fd);
                assert(it.get() != NULL);
                if(epollEventVector_[i].events & EPOLLERR)
                    it->handleClose();
                else
				if(epollEventVector_[i].events & EPOLLIN )
                {
                    //peer shutdown trigger EPOLLIN&EPOLLRDHUP
                    if(epollEventVector_[i].events & EPOLLRDHUP)
                        it->handleClose();
                    else
                        it->handleRead();
				}
				else
					if(epollEventVector_[i].events & EPOLLOUT)
					{
                #ifdef NET_DEBUG
                       printf("%s\n",it->getName().c_str());
                #endif
                       it->handleWrite();
					}

			}
            if(ret == static_cast<int>(epollEventVector_.size()))
                epollEventVector_.resize(ret*2);//a simple method to deal with events num.
		}
		else
        {
			if(!isRunning_)
				break;
            if(errno == EINTR)
                continue;
        }
    }
}

#endif //linux
